package transcoding

import java.io.IOException
import java.net.URLConnection

import org.codehaus.jackson.annotate.JsonAutoDetect
import org.codehaus.jackson.annotate.JsonMethod
import org.codehaus.jackson.annotate.JsonProperty
import scala.annotation.target.field

import com.xuggle.xuggler.ICodec
import com.xuggle.xuggler.IContainer
import com.xuggle.xuggler.IStream

import play.api.Logger
import collection.JavaConversions._

abstract class MediaType
case object Video extends MediaType
case object Audio extends MediaType
case object Image extends MediaType
case object Other extends MediaType

@JsonAutoDetect(Array(JsonMethod.NONE))
case class StreamInfo(@(JsonProperty @field) val sampleRate: Option[Int], @(JsonProperty @field) val codec: Option[ICodec.ID])

@JsonAutoDetect(Array(JsonMethod.NONE))
class MediaFile(@(JsonProperty @field) val filePath: String, container: Option[IContainer]) {

  def this(filePath: String) = this(filePath, None)
  def this(container: IContainer) = this(container.getURL, Some(container))

  /**
   * the following private method
   * are only valid during construction because
   * afterwards the container is closed (to save memory)
   */
  private def allStreams = { container.map(cont => (0 to cont.getNumStreams() - 1).map(i => cont.getStream(i))) }
  private def videoStream: Option[IStream] = {
    allStreams.map(seq => seq.find(s => s.getStreamCoder.getCodec.getType.equals(ICodec.Type.CODEC_TYPE_VIDEO)))
      .getOrElse(None)
  }
  private def audioStream: Option[IStream] = {
    allStreams.map(seq => seq.find(s => s.getStreamCoder.getCodec.getType.equals(ICodec.Type.CODEC_TYPE_AUDIO)))
      .getOrElse(None)
  }

  val format = container.map(cont => cont.getContainerFormat)
  val metaData = container.map(cont => cont.getMetaData)

  @JsonProperty
  val streamInfo = {
    val videoStreamCodec = videoStream.map(
      vs => vs.getStreamCoder.getCodec.getID)
    val audioStreamCodec = audioStream.map(
      as => as.getStreamCoder.getCodec.getID)
    val audioSampleRate = audioStream.map(
      as => as.getStreamCoder.getSampleRate)

    Map(Audio -> new StreamInfo(audioSampleRate, audioStreamCodec),
      Video -> new StreamInfo(None, videoStreamCodec))
  }

  @JsonProperty("format")
  def json_format = format.map(
    frmt => frmt.getInputFormatShortName())

  @JsonProperty("metaData")
  def json_MetaData = metaData.map(
    md => {
      md.getKeys.map(k => Map(k -> md.getValue(k)))
    })

  /*IContainer container = IContainer.make();
 * if (container.open("myfile.flv", IContainer.Type.WRITE, null) <0)
 *
 * &nbsp;&nbsp;throw new RuntimeException("failed to open");
 * IStream stream = container.addNewStream(0);
 * IStreamCoder coder = stream.getStreamCoder();
 * coder.setCodec(ICodec.ID.CODEC_ID_MP3);
 * coder.setSampleRate(22050);
 * coder.setChannels(2);
 * coder.setBitRate(64000);
 * if (coder.open()<0) throw new RuntimeException("could not open coder");
 *
 * if (container.writeHeader() < 0) throw new RuntimeException();
 * IPacket packet = IPacket.make();
 * while( ... have more data to process ... ) {
 *
 * &nbsp;&nbsp;then assuming it generated an IPacket for you...
 * &nbsp;&nbsp;if (container.writePacket(packet)<0)
 * &nbsp;&nbsp;&nbsp;&nbsp;throw new RuntimeException("could not write
 * packet");
 * }
 * if (container.writeTrailer() <0) throw new RuntimeException();
 * container.close();
 */
  /*def headers = {

    def getHeaders(container: IContainer) = {
      
      Logger.debug(container.getMetaData.toString)
      
      val outContainer = IContainer.make
      val outStream = new ByteArrayOutputStream
//      if (outContainer.open(outStream, inputFormat.get) < 0)
//        throw new RuntimeException("failed to open to write headers")
      if (outContainer.open(filePath+"_header.flv", IContainer.Type.WRITE, null) < 0)
        throw new RuntimeException("failed to open to write headers")
      outContainer.setFormat(inputFormat.get)
      val stream = outContainer.addNewStream(0)
      val streamCoder = IStreamCoder.make(IStreamCoder.Direction.ENCODING)
      streamCoder.setCodec(this.audioStream.get.getStreamCoder.getCodec)
      stream.setStreamCoder(streamCoder)
      
      streamCoder.setSampleRate(22050)
      streamCoder.setChannels(2)
      streamCoder.setBitRate(64000)
      if (streamCoder.open()<0) throw new RuntimeException("could not open coder")
      if (outContainer.writeHeader() < 0) throw new RuntimeException("could not write headers");
      if (outContainer.writeTrailer() <0) throw new RuntimeException("could not write trailer");
      
      outContainer.close();
      Logger.debug("WROTE "+filePath+"_header.flv")
      
      ""
    }

    container.map(cont => getHeaders(cont))
  }*/

  @JsonProperty
  val bitRate = {
    container.map(cont => cont.getBitRate)
  }
  @JsonProperty
  val duration = {
    container.map(cont => cont.getDuration() / 1000000)
  }
  @JsonProperty
  /**
   * returning List instead of a tuple because
   * jackson has problems with it otherwise
   */
  val videoDimensions = {
    videoStream match {
      case None => List(0, 0)
      case Some(coder) =>
        val sc = coder.getStreamCoder()
        List(sc.getWidth(), sc.getHeight())
    }
  }

  val mediaType: MediaType = {
    if (isVideo) Video
    else if (isAudio) Audio
    else if (isImage) Image
    else Other
  }

  @JsonProperty("mediaType")
  def jsonMediaType = mediaType.toString

  @JsonProperty
  def mimeType = {
    val defMIME: String = MediaFile.getMIMEType(filePath)
    if (defMIME == null) {
      val typeMIME = format.map(inputf => inputf.getInputFormatShortName().split(",").headOption)
        .getOrElse(Some("unknown")).get
      val typeFormat: String = mediaType match {
        case Video => "video"
        case Audio => "audio"
        case Image => "image"
        case Other => "other"
      }

      "%s/%s".format(MediaFile.NON_MEDIA_TYPE.getOrElse(typeMIME, typeFormat), MediaFile.MIME_TYPES.getOrElse(typeMIME, typeMIME))
    } else defMIME
  }

  lazy val isVideo = {
    def hasTXTMime = {
      val mime = MediaFile.getMIMEType(filePath)
      mime != null && mime.startsWith("text") //<- Xuggler bug
    }

    !videoStream.isEmpty && !hasTXTMime
  }

  lazy val isAudio = {
    !isVideo &&
      !audioStream.isEmpty
  }

  lazy val isImage = {
    def hasImageMIME = {
      val mime = MediaFile.getMIMEType(filePath)
      mime != null && mime.startsWith("image")
    }

    !isVideo && !isAudio && hasImageMIME
  }

  override def equals(other: Any): Boolean = {
    if (other.isInstanceOf[MediaFile]) {
      this.filePath == other.asInstanceOf[MediaFile].filePath
    } else
      false
  }

  @JsonProperty("mediaID")
  override def hashCode = filePath.hashCode
}

object MediaFile {

  private val MIME_TYPES = Map("mp3" -> "mpeg",
    "oga" -> "ogg",
    "ogx" -> "ogg",
    "aac" -> "mp4",
    "quicktime" -> "mpeg",
    "mov" -> "mpeg",
    "mkv" -> "x-matroska")
  private val NON_MEDIA_TYPE = Map("ogx" -> "application", "gif" -> "image")
  //            ,
  //            {"ogg", "audio/ogg"},
  //            {"oga", "audio/ogg"},
  //            {"ogx", "application/ogg"},
  //            {"aac", "audio/mp4"},
  //            {"m4a", "audio/mp4"},
  //            {"flac", "audio/flac"},
  //            {"wav", "audio/x-wav"},
  //            {"wma", "audio/x-ms-wma"},
  //            {"ape", "audio/x-monkeys-audio"},
  //            {"mpc", "audio/x-musepack"},
  //            {"shn", "audio/x-shn"},
  //
  //            {"flv", "video/x-flv"},
  //            {"avi", "video/avi"},
  //            {"mpg", "video/mpeg"},
  //            {"mpeg", "video/mpeg"},
  //            {"mp4", "video/mp4"},
  //            {"m4v", "video/x-m4v"},
  //            {"mkv", "video/x-matroska"},
  //            {"mov", "video/quicktime"},
  //            {"wmv", "video/x-ms-wmv"},
  //            {"ogv", "video/ogg"},
  //            {"divx", "video/divx"},
  //            {"m2ts", "video/MP2T"},
  //
  //            {"gif", "image/gif"},
  //            {"jpg", "image/jpeg"},
  //            {"jpeg", "image/jpeg"},
  //            {"png", "image/png"},
  //            {"bmp", "image/bmp"},
  //    );

  val MIME_MAPS = URLConnection.getFileNameMap()

  def orElse[A](equalThen: (A, A))(actualVal: A): A = {
    if (actualVal.equals(equalThen._1))
      equalThen._2
    else actualVal
  }

  def apply(filePath: String): MediaFile = {
    val file = new java.io.File(filePath)
    if (!file.exists()) {
      throw new IOException("File '%s' does not exists".format(file.getAbsolutePath))
    }
    apply(file)
  }

  def apply(file: java.io.File): MediaFile = {
    val container = IContainer.make()
    val absolutePath = file.getAbsolutePath
    try {

      val suffix = getFileSuffix(file.getName).getOrElse("")
      //FIXME: getNumStreasm crashes the JVM for some non-media type
      if (!List("3gp", "flv", "mov", "avi", "mkv", "mpg", "mpg4", "wav", "wma", "ogg", "oga", "mp4").contains(suffix))
        throw new IOException("Not supported Media File %s".format(file.getAbsolutePath))

      if (container.open(file.getAbsolutePath, IContainer.Type.READ, null) < 0)
        throw new IOException("Could not open file '%s' for reading. Is it an audio/video file?".format(file.getAbsolutePath))

      new MediaFile(container)
    } catch {
      case error: IOException =>
        Logger.trace("Received error while creating MediaFile, returning simple media file (%s)".format(error.getMessage))
        new MediaFile(file.getAbsolutePath)
    } finally {
      container.close
      container.delete
    }
  }

  val transcodingCmds = Map("video" -> "/home/giacomo/Code/ffmpeg -ss $offset -i $file -async 1 -b $bitrate -s $size -ar 44100 -ac 2 -v 0 -f flv -vcodec libx264 -preset superfast -threads 0 -")

  def getMIMEType(filePath: String) = {
    MIME_MAPS.getContentTypeFor(filePath)
  }

  def getFileSuffix(fileName: String): Option[String] = {
    val nameSeg = fileName.split('.')
    if (nameSeg.isEmpty) None
    else Some(nameSeg(nameSeg.length - 1))
  }
}